package fate.xp3;

import fate.xp3.FateDecrypter;
import fate.ZlibUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.DataFormatException;
import java.util.zip.InflaterInputStream;

import static fate.BinaryUtils.*;

public class XP3Reader {
    private byte[] buffer = new byte[128];

    private final String path;
    private final List<XP3Entry> entries;
    private final XP3Decrypter decrypter;

    public XP3Reader(String path, XP3Decrypter decrypter) throws IOException, DataFormatException {
        this.path = path;
        this.decrypter = decrypter;
        RandomAccessFile file = new RandomAccessFile(path, "r");
        try {
            this.entries = findEntries(file);
        } finally {
            file.close();
        }
    }

    public XP3Reader(String path) throws IOException, DataFormatException {
        this(path, new FateDecrypter()); // should I use fate decrypter (or null) by default?
    }

    private InputStream fileToStream(final RandomAccessFile file) {
        return new InputStream() {
            @Override
            public int read() throws IOException {
                return file.read();
            }
        };
    }

    private String readUTF16LE(InputStream in) throws IOException {
        int n = readushort(in, buffer);
        byte[] b = new byte[n*2];
        in.read(b);
        return new String(b, "UTF-16LE");
    }

    private XP3Segment findSegment(InputStream z) throws IOException {
        boolean compressed = readuint(z, buffer) != 0;
        long offset = readulong(z, buffer);
        long original_size = readulong(z, buffer);
        long compressed_size = readulong(z, buffer);
        return new XP3Segment(compressed, offset, original_size, compressed_size);
    }
    private XP3Entry findEntry(InputStream z) throws IOException {
        skipStr(z, "File", buffer);
        long entry_size = readulong(z, buffer);

        // start entry file block

        skipStr(z, "info", buffer);
        long info_size = readulong(z, buffer);

        // start info block

        boolean encrypted = readuint(z, buffer) != 0;
        long original_size = readulong(z, buffer);
        long compressed_size = readulong(z, buffer);
        String name = readUTF16LE(z);
        if (info_size != name.length() * 2 + 22) {
            throw new IOException("XP3 Assert failure");
        }

        // end of info block

        skipStr(z, "segm", buffer);
        long segment_number = readulong(z, buffer)/28;

        long sum_compressed_size = 0;
        long sum_original_size = 0;

        ArrayList<XP3Segment> segments = new ArrayList<XP3Segment>();
        for (int i = 0; i < segment_number; i++) {
            XP3Segment segment = findSegment(z);
            segments.add(segment);
            sum_original_size += segment.getOriginalSize();
            sum_compressed_size += segment.getCompressedSize();
        }

        if (original_size != sum_original_size || compressed_size != sum_compressed_size) {
            throw new IOException();
        }

        skipStr(z, "adlr", buffer);
        if (readulong(z, buffer) != 4) {
            throw new IOException();
        }
        int adler = readuint(z, buffer);

        // end of entry file block

        if (entry_size != name.length()*2 + segment_number*28 + 62) {
            throw new IOException();
        }

        return new XP3Entry(path, encrypted, original_size, compressed_size, name, segments, adler, decrypter);
    }

    private List<XP3Entry> findEntries(RandomAccessFile file) throws IOException, DataFormatException {
        long size = file.length();

        skipStr(file, "XP3\r\n \n\u001a\u008bg\u0001", buffer);

        long index_offset = readulong(file, buffer);

        file.seek(index_offset);
        if (file.read() != 1) {
            throw new RuntimeException("Invalid XP3 format");
        }
        long compressed_size = readulong(file, buffer);
        long original_size = readulong(file, buffer);
        if (index_offset + compressed_size + 17 != size) {
            throw new IOException("XP3 Assert failure");
        }

//        InflaterInputStream z = new InflaterInputStream(fileToStream(file));
        
        byte[] compressed = new byte[(int)compressed_size];
        file.readFully(compressed);
        byte[] original = ZlibUtils.uncompress(compressed, (int)original_size);
        InputStream z = new ByteArrayInputStream(original);

//        System.out.println(compressed_size);
//        System.out.println(original_size);

        long uncompressed = 0;
        ArrayList<XP3Entry> entries = new ArrayList<XP3Entry>();
        while (uncompressed < original_size) {
//            System.out.println(uncompressed);
            XP3Entry e = findEntry(z);
            entries.add(e);
            uncompressed += e.getName().length()*2 + e.getSegments().size()*28 + 62 + 12;
        }

        return entries;
    }

    public List<XP3Entry> getEntries() {
        return entries;
    }


    public static void main(String args[]) throws IOException, DataFormatException {
        XP3Reader read = new XP3Reader("D:\\Projects\\Special\\libfate\\libfate-py\\bgm.xp3");
        long start = System.currentTimeMillis();
        List<XP3Entry> entries = read.getEntries();
        for (XP3Entry entry: entries) {
//            System.out.println(entry.getName());
//            System.out.println(new String(entry.read(), "cp942"));
            entry.read();
        }
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }

}
